package hyl.base.ntty;

import hyl.core.MyFun;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * 搭建整体处理框架 （带主子线程） 规定了一个服务只处理一种模型，模型大小不限
 */
public class NettyServer {
	/*
	 * 服务端由两种线程池， 用于Acceptor的React主线程(bossGroup)和用于I/O操作的React从线程池(workerGroup)；
	 * 客户端只有用于连接及IO操作的React的主线程池；
	 */
	private EventLoopGroup bossGroup;
	private EventLoopGroup workerGroup;
	private int port;
	private String host;
	private ChannelHandler _model;

	private NettyServer(String host, int port) {
		this.host = host;
		this.port = port;
	}

	public static NettyServer getInstance(int port) {
		return new NettyServer("localhost", port);
	}

	public static NettyServer getInstance(String host, int port) {
		return new NettyServer(host, port);
	}

	public NettyServer addInitializer(ChannelHandler control) {
		_model = control;
		return this;
	}

	/** 规定了一个服务只处理一种模型 */
	@SuppressWarnings("finally")
	public boolean start() {
		if (_model == null)
			return false;
		if (bossGroup == null)
			/*
			 * 2.创建两个线程池 第一个 监听端口号 nio监听 EventLoopGroup继承与ScheduledExecutorService， 有定时执行的味道
			 * 其中boss用来监控 端口请求,worker用来处理io事件. 具体的说： boss执行 server.accept()操作
			 * worker处理事件的读写到业务逻辑处理等后续操作.
			 */
			bossGroup = new NioEventLoopGroup(1);
		if (workerGroup == null)
			workerGroup = new NioEventLoopGroup();
		try {
			// 1.创建服务对象
			// 使用ServerBootstrap类来初始化netty服务器，并且开始监听端口的socket请求
			ServerBootstrap bootstrap = new ServerBootstrap();
			// 2.绑定专用线程池
			bootstrap.group(bossGroup, workerGroup);
			/*
			 * 4.1 修改配置 Option是为了NioServerSocketChannel设置的，用来接收传入连接的
			 */
			bootstrap.option(ChannelOption.SO_BACKLOG, 1024)//
					// 连接数，用于临时存放已完成三次握手的请求的队列的最大长度
					// 第2次握手服务端向客户端发送请求确认，同时把此连接放入队列A中，
					// 然后客户端接受到服务端返回的请求后，再次向服务端发送请求，表示准备完毕，此时服务端收到请求，把这个连接从队列A移动到队列B中，
					// 此时A+B的总数，不能超过SO_BACKLOG的数值，满了之后无法建立新的TCP连接,2次握手后和3次握手后的总数
					// 当服务端从队列B中按照FIFO的原则获取到连接并且建立连接[ServerSocket.accept()]后，B中对应的连接会被移除，这样A+B的数值就会变小
					// 此参数对于程序的连接数没影响，会影响正在准备建立连接的握手。

					.option(ChannelOption.SO_REUSEADDR, true)
					// 是否允许重复绑定端口，重复启动，会把端口从上一个使用者上抢过来
					.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000)// 连接超时30000毫秒
			;
			bootstrap.channel(NioServerSocketChannel.class);
			/*
			 * 5. 实现worker线程处理的接口 child=》worker
			 * 
			 * option / handler / attr方法都定义在AbstractBootstrap中，
			 * 
			 * 所以服务端和客户端的引导类方法调用都是调用的父类的对应方法。
			 * 
			 * option： 设置通道的选项参数， 对于服务端而言就是ServerSocketChannel， 客户端而言就是SocketChannel；
			 * 
			 * handler： 设置主通道的处理器， 对于服务端而言就是ServerSocketChannel，也就是用来处理Acceptor的操作；
			 * 
			 * handler： 对于客户端的SocketChannel，主要是用来处理 业务操作；
			 * 
			 * attr： 设置通道的属性；
			 * 
			 * childHandler / childOption / childAttr 方法（只有服务端ServerBootstrap才有child类型的方法）
			 * 
			 * 对于服务端而言，有两种通道需要处理， 一种是ServerSocketChannel：用于处理用户连接的握手操作，
			 * 另一种是SocketChannel，表示对应客户端连接后的处理。
			 * 
			 * 而对于客户端，一般都只有一种channel，也就是SocketChannel。
			 * 
			 * handler在初始化时就会执行，而childHandler会在客户端成功connect后才执行，这是两者的区别。
			 */
			// if (control==null)
			// control=new HelloWorldHandler();
			// bootstrap.childHandler(control);
			bootstrap.childOption(ChannelOption.SO_SNDBUF, 1048576 * 8)
					// 发送缓存大小默认8M

					.childOption(ChannelOption.SO_KEEPALIVE, true)
					// 启用心跳，双方TCP套接字建立连接后（即都进入ESTABLISHED状态），
					// 并且在两个小时左右上层没有任何数据传输的情况下，这套机制才会被激活，TCP会自动发送一个活动探测数据报文
					.childOption(ChannelOption.TCP_NODELAY, true)
			// TCP协议中，TCP总是希望每次发送的数据足够大，避免网络中充满了小数据块。
			// Nagle算法就是为了尽可能的发送大数据快。
			// TCP_NODELAY就是控制是否启用Nagle算法。
			// 如果要求高实时性，有数据发送时就马上发送，就将该选项设置为true关闭Nagle算法；
			// 如果要减少发送次数减少网络交互，就设置为false等累积一定大小后再发送。默认为false。
			// .childOption(ChannelOption.SO_TIMEOUT, 5000);// 不支持
			;
			bootstrap.childHandler(_model);
			// 6.配置好服务器，在服务器启动时绑定闯入的port端口，等待同步
			ChannelFuture channelFuture = null;
			if (host == null)
				channelFuture = bootstrap.bind(port).sync();
			else
				channelFuture = bootstrap.bind(host, port).sync();
			
			System.out.println(MyFun.join("短信服务已启动。。。服务IP=", host, ",端口", port));

			// 7.如果绑定成功 发起异步连接操作
			/*
			 * channelFuture.addListener(new ChannelFutureListener() {
			 * 
			 * @Override public void operationComplete(ChannelFuture future) { if
			 * (future.isSuccess()) {
			 * 
			 * } else { // 输出错误信息 Throwable cause = future.cause(); cause.printStackTrace();
			 * // do something.... } } });
			 */
			// 8.等待服务端监听端口关闭
			channelFuture.channel().closeFuture().sync();

		} catch (Exception e) {
			e.printStackTrace();
			return false;
		} finally {
			// 优雅释放
			bossGroup.shutdownGracefully();
			workerGroup.shutdownGracefully();
			return true;
		}
	}

}
